﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Transactions;
using Grundy.Common;
using Grundy.Entity;
using Grundy.Message;
using log4net;

namespace Grundy.Licensing.Server
{
    public class ExecutionCounter
    {
        private readonly TimeSpan _flushOnceEvery;
        private readonly ConcurrentDictionary<FeatureIdentificationKey, ExecutionCount> _executionCounter;
        private static ILog _logger = LogManager.GetLogger(typeof(ExecutionCounter));

        public ExecutionCounter(List<ExecutionCount> executions, TimeSpan flushOnceEvery)
        {
            _flushOnceEvery = flushOnceEvery;
            var dict = (executions ?? new List<ExecutionCount>());
            _executionCounter = new ConcurrentDictionary<FeatureIdentificationKey, ExecutionCount>(dict.Select(i => new KeyValuePair<FeatureIdentificationKey, ExecutionCount>(i.FeatureIdentificationKey, i)));

            InitializeMaintenanceTimer();
        }

        private void InitializeMaintenanceTimer()
        {
            _logger.Info("Initialized maintenance timer. Will execute every {0} seconds.".FormatWith(_flushOnceEvery.TotalSeconds));
            _maintenanceTimer = new Timer(o => Save(), null, (int)_flushOnceEvery.TotalMilliseconds, (int)_flushOnceEvery.TotalMilliseconds);
        }

        public void Save()
        {
            // Concurrency issue here, sessions may not be wriiten to database at all. However, the execution coun remains consistent.
            var values = _executionCounter.Values;

            try
            {
                _logger.Info("Started writing execution counts to license database.");
                    
                using (var db = new LicenseDatabase())
                {
                    db.Configuration.AutoDetectChangesEnabled = false;

                    foreach (var executionCount in values)
                    {
                        var loaded = db.ExecutionCounts.Find(executionCount.Id);
                        if (loaded == null)
                        {
                            db.ExecutionCounts.Add(executionCount);
                        }
                        else
                        {
                            loaded.Executions = executionCount.Executions;
                            foreach (var clientSession in executionCount.ClientSessions)
                            {
                                loaded.ClientSessions.Add(clientSession);
                            }
                        }
                    }
                    db.SaveChanges();
                }
                values.ForEachItemDo(i => i.ClientSessions.Clear());
                    
                _logger.Info("Finished writing execution counts to license database.");
            }
            catch (Exception ex)
            {   
                _logger.ErrorFormat("Could not write execution counts to license database. Message:{0} \r\nDetails: {1}", ex.Message, ex);
            }
            
        }

        private object _syncLock = new object();
        private Timer _maintenanceTimer;

        public Tuple<bool, long> CheckAndIncrement(FeatureIdentificationKey fik, long limit, LicenseRequestInformation lri)
        {
            var clientSession = GetClientSession(lri);
            
            if (_executionCounter.ContainsKey(fik))
            {
                var executions = _executionCounter[fik];
                lock (_syncLock)
                {
                    if (executions.Executions < limit)
                    {
                        executions.Add(clientSession);
                        return Tuple.Create(true, executions.Executions);
                    }
                    else
                    {
                        return Tuple.Create(false, executions.Executions);
                    }   
                }
            }
            else
            {
                var executionCount = new ExecutionCount(fik, 0, new List<ClientSession>());
                executionCount.Add(clientSession);
                _executionCounter.AddOrUpdate(fik, executionCount, (f,e) => 
                {
                    var ec = e; 
                    ec.Add(clientSession);
                    return ec;
                });
                return Tuple.Create(true, executionCount.Executions);
            }
        }

        private ClientSession GetClientSession(LicenseRequestInformation lri)
        {
            return new ClientSession{Id = Guid.NewGuid(), EndPoint = lri.EndPoint, FeatureIdentificationKey = lri.FeatureIdentificationKey, Identifiers = lri.ClientIdentifiers};
        }

        public ExecutionCount GetExecutionCount(FeatureIdentificationKey fik)
        {
            if (_executionCounter.ContainsKey(fik))
            {
                return _executionCounter[fik];
            }
            return new ExecutionCount();
        }
    }
}